﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using System.IO;
using System.Reflection;

namespace Services
{
    /// <summary>
    /// Classe de service (singleton) qui permet de savoir si un champ est conforme à une regex.
    /// Le dictionnaire de Regex est initialisé avec le fichier XML regexConfig.xml.
    /// Ainsi un utilisateur peut ajouter une regex à la main et en supprimer.
    /// </summary>
    public class RegexService
    {
        #region Attribut(s)

        /// <summary>
        /// Objet permettant d'assurer le fait que RegexService soit Thread-Safe
        /// </summary>
        private static readonly object padlock = new object();

        /// <summary>
        /// Instance statique de RegexService permettant d'implémenter le pattern Singleton
        /// </summary>
        private static RegexService instance = null;

        /// <summary>
        /// 
        /// </summary>
        //private ReadAndWrite.ReadAndWrite read;

        #endregion
        
        #region Propriété(s)

        /// <summary>
        /// Permet de s'assurer qu'une unique instance de RegexService soit créer.
        /// </summary>
        public static RegexService Instance
        {
            get
            {
                lock (padlock)
                {
                    if (instance == null)
                    {
                        lock (padlock)
                        {
                            instance = new RegexService();
                        }
                    }
                }
                return instance;
            }
        }

        /// <summary>
        /// Dictionnaire des regexs
        /// </summary>
        public Dictionary<string, string> Regex
        {
            get;
            private set;
        }

        #endregion

        #region Constructeur(s)

        /// <summary>
        /// Constructeur sans paramètre.
        /// Initialise le dictionnaire de regex avec le fichier regexConfig
        /// </summary>
        private RegexService()
        {
            // PLUS TARD : read = new ReadAndWrite.ReadAndWriteXML();
            Regex = new Dictionary<string, string>();
            Init();
        }

        #endregion

        #region Méthode(s)

        #region Initialise la map de regex

        /// <summary>
        /// Initialise le dictionnaire de regex en parsant le fichier XML.
        /// </summary>
        private void Init()
        {
            Assembly _ass = Assembly.GetExecutingAssembly();
            Stream _stream = _ass.GetManifestResourceStream(_ass.GetName().Name + ".RegexService.regexConfig.xml");
            //XmlNodeList list = DocList(@"regexConfig.xml");
            XmlNodeList list = DocList(_stream);

            try
            {
                for (int i = 0; i < list.Count; i++)
                {
                    String key = list[i].Attributes["id"].Value;
                    String value = list[i].InnerText;
                    Regex.Add(key, value);
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// Permet de récupérer tous les noeuds regex du fichier
        /// </summary>
        /// <param name="filename">Chemin du fichier</param>
        /// <returns>Retoune le contenu de toutes les balises <regex></regex> </returns>
        private static XmlNodeList DocList(string filename)
        {
            XmlDocument document = new XmlDocument();
            try
            {
                document.Load(filename);
                XmlNode node = document.DocumentElement;
                XmlNodeList nodeList = node.SelectNodes("regex");
                return nodeList;
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// Permet de récupérer tous les noeuds regex du fichier
        /// </summary>
        /// <param name="stream">Flux du fichier de configuration</param>
        /// <returns>Retoune le contenu de toutes les balises <regex></regex> </returns>
        private static XmlNodeList DocList(Stream stream)
        {
            XmlDocument document = new XmlDocument();
            try
            {
                document.Load(stream);
                XmlNode node = document.DocumentElement;
                XmlNodeList nodeList = node.SelectNodes("regex");
                return nodeList;
            }
            catch (Exception)
            {
                throw;
            }
        }

        #endregion

        #region Méthodes Add / remove regex tempory

        /// <summary>
        /// Permet d'ajouter une regex au dictionnaire temporairement, elle sera pas enregistré dans le fichier.
        /// </summary>
        /// <param name="key">Clé de la regex, permettant de la retrouvé par la suite</param>
        /// <param name="value">valeur de l'expression régulière</param>
        /// <returns>Retourne un booléen permettant de savoir si tout c'est bien passé</returns>
        public bool AddRegex(String key, String value)
        {
            try
            {
                Regex.Add(key, value);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// Pemet de supprimer une regex du dictionnaire temporairement, sans répercussion dans le fichier.
        /// </summary>
        /// <param name="key">Clé de la regex a retiré du dictionnaire</param>
        /// <returns>Retourne un booléen permettant de savoir si tout c'est bien passé</returns>
        public bool RemoveRegex(String key)
        {
            try
            {
                Regex.Remove(key);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        #endregion

        #region Méthodes Add Regex Params

        /// <summary>
        /// Permet d'ajouter une regex DECIMAL prenant en paramètre le nombre d'entier et de décimals acceptable.
        /// Cela équivaut à NUMBER(X,Y) en base de données.
        /// La clé de la regex sera construite de la sorte DECIMALX_Y.
        /// Si la clé exsite déjà la méthode renvoi false.
        /// </summary>
        /// <param name="_precision">Précision (Ex : NUMBER(5,2) = Précision à 5)</param>
        /// <param name="_scale">Partie décimal (Ex : NUMBER(5,2) = Scale à 2)</param>
        /// <returns>Retourne le résultat de la méthode</returns>
        public bool AddDecimal(int _precision, int _scale)
        {
            // Clé de la nouvelle regex
            String key = @"DECIMAL" +
                            _precision +
                            @"_" + _scale;

            // Valeur de la regex
            String regex = @"^(^\d{1," + (_precision - _scale) +
                            @"}$)|(^\d{1," + (_precision - _scale) +
                            @"},\d{0," + _scale + @"}$)$";

            // Si la regex existe déjà : renvoi false
            try
            {
                Regex.Add(key, regex);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// Permet d'ajouter une regex CHAINE prenant en paramètre la longueur de celle ci.
        /// Cela équivaut à VARCHAR2(X) en base de données.
        /// La clé de la regex sera construite de la sorte CHAINE_X
        /// Si la clé exsite déjà la méthode renvoi false.
        /// </summary>
        /// <param name="_length">Longueur de la chaine</param>
        /// <returns>Retourne le résultat de la méthode</returns>
        public bool AddVarchar(int _length)
        {
            // Clé de la nouvelle regex
            String key = @"CHAINE_" +
                            _length;

            // Valeur de la regex
            String regex = @"^.{0," + _length + @"}$";

            // Si la regex existe déjà : renvoi false
            try
            {
                Regex.Add(key, regex);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }
        
        /// <summary>
        /// Permet d'ajouter une regex NUMBER prenant en paramètre la partie entière.
        /// Cela équivaut à NUMBER(X) en base de données.
        /// La clé de la regex sera construite de la sorte NUMBERX.
        /// Si la clé exsite déjà la méthode renvoi false.
        /// </summary>
        /// <param name="_length">Longueur du nombre</param>
        /// <returns>Retourne le résultat de la méthode</returns>
        public bool AddNumber(int _length)
        {
            // Clé de la nouvelle regex
            String key = @"NUMBER" +
                            _length;

            // Valeur de la regex
            String regex = @"^[0-9]{0," + _length  + @"}$";

            // Si la regex existe déjà : renvoi false
            try
            {
                Regex.Add(key, regex);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        #endregion

        #region IsMatch

        /// <summary>
        /// Contrôle d'un champ permettant de savoir si le champ passé en paramètre correspond à la regex demandé par l'utilisateur.
        /// </summary>
        /// <param name="key">Clé de la regex à utiliser parmi le dictionnaire de regex. Si la regex n'est pas connu, retourne false</param>
        /// <param name="texte">Valeur que l'utilisateur veut checker.</param>
        /// <returns>Retourne si la valeur match avec la valeur de la regex correspondant à la clé.</returns>
        public bool IsMatch(String key, String texte)
        {
            try
            {
                String valueRegex = Regex.First(f => f.Key.Equals(key)).Value;
                Regex rg = new Regex(valueRegex);
                return rg.IsMatch(texte);
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// Ajoute dans le service si elle est pas déjà présente la regex formatté correctement et retourne le résultat de l'opération
        /// </summary>
        /// <param name="_precision">Précision (Ex : NUMBER(5,2) = Précision à 5)</param>
        /// <param name="_scale">Partie décimal (Ex : NUMBER(5,2) = Scale à 2)</param>
        /// <param name="texte">Chaine à vérifier</param>
        /// <returns>Retourne le résultat de l'opération</returns>
        public bool IsMatchDecimal(Int32 _precision, Int32 _scale, String texte)
        {
            this.AddDecimal(_precision, _scale);
            String key = String.Format("DECIMAL{0}_{1}", _precision, _scale);
            return IsMatch(key, texte);
        }

        #endregion

        #region Utilitaires

        /// <summary>
        /// Permet de logger le contenu du dictionnaire de regex.
        /// Si path est null, la log s'effectue dans la Console sinon dans le fichier
        /// </summary>
        /// <param name="path">Chemin du fichier de log.</param>
        public void DisplayDictionnary(String path)
        {
            StringBuilder sb = new StringBuilder();

            foreach (KeyValuePair<String, String> kv in Regex)
            {
                if (path == null)
                    System.Console.WriteLine("KEY : " + kv.Key + " : VALUE : " + kv.Value);
                else
                    sb.AppendLine("KEY : " + kv.Key + " : VALUE : " + kv.Value);
            }

            if (path != null)
                File.WriteAllText(path, sb.ToString());
        }
        
        #endregion

        #endregion
    }
}